﻿Imports System.Threading
Imports System.Runtime.InteropServices
Imports System.Windows.Interop

Class MainWindow
    Private Const WM_SYSCOMMAND As UInteger = &H112
    Private Const SC_MONITORPOWER As Integer = &HF170
    Private Const MONITOR_OFF As Integer = 2

    Private Const ES_AWAYMODE_REQUIRED As UInteger = &H40
    Private Const ES_CONTINUOUS As Integer = &H80000000
    Private Const ES_DISPLAY_REQUIRED As UInteger = &H2
    Private Const ES_SYSTEM_REQUIRED As UInteger = &H1

    Private Const SECONDS As Integer = 1000
    Private Const MINUTES As Integer = 60000

    Dim clockUpdateTimer As Timer
    Dim backgroundSoundTimer As Timer
    Dim preventSleepingTimer As Timer
    Dim trayIcon As Forms.NotifyIcon

    Dim sched As Quartz.IScheduler
    Dim job As Quartz.Impl.JobDetailImpl

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
    Private Shared Function SetThreadExecutionState(esFlags As Integer) As UInt32
    End Function

    Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
        Dim schedFact As Quartz.ISchedulerFactory

        AddHandler NICPoller.NicChange, AddressOf DockChanged
        AddHandler NICPoller.BatteryLevel, AddressOf BatteryChanged
        AddHandler settings.SettingsChanged, AddressOf SettingsChanged

        ' Set up Quartz scheduler
        schedFact = New Quartz.Impl.StdSchedulerFactory
        sched = schedFact.GetScheduler
        sched.Start()

        ' Set up Quartz job for alarm
        job = New Quartz.Impl.JobDetailImpl("clockJob", "clockGroup", GetType(AlarmJob))
        With job
            .Key = New Quartz.JobKey("clockKey")
            .JobDataMap.Add("Dispatcher", Me.Dispatcher)
            .JobDataMap.Add("Instance", Me)
        End With

        SetupDockIcon()

        ' Apply the setting to prevent the computer from sleeping on a regular basis
        preventSleepingTimer = New Timer(AddressOf PreventSleeping, Nothing, 0, MINUTES * 5)

    End Sub

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        clockUpdateTimer = New System.Threading.Timer(AddressOf UpdateTime, Me.Dispatcher, 0, SECONDS * 2)
        trayIcon.Visible = True

        LoadBackground()
        LoadBackgroundSound()
        SetAlarm()

    End Sub

    Private Sub MainWindow_Closing(sender As Object, e As ComponentModel.CancelEventArgs) Handles Me.Closing
        My.Settings.Save()
        trayIcon.Visible = False
        RestoreSleeping()

    End Sub

    Private Sub cmdBlankScreen_Click(sender As Object, e As RoutedEventArgs) Handles cmdBlankScreen.Click
        PowerOffMonitor()
    End Sub

    Private Sub cmdClose_Click(sender As Object, e As RoutedEventArgs) Handles cmdClose.Click
        HideScreen()
    End Sub

    Private Sub cmdOptions_Click(sender As Object, e As RoutedEventArgs) Handles cmdOptions.Click
        If settings.Opacity = 0 Then
            settings.FadeIn()
        Else
            settings.FadeOut()
        End If

    End Sub

    Private Sub cmdConfirmAlarm_Click(sender As Object, e As RoutedEventArgs) Handles cmdConfirmAlarm.Click
        HideAlarmNotification()
        SetAlarm()

    End Sub

    Private Sub cmdDelayAlarm_Click(sender As Object, e As RoutedEventArgs) Handles cmdDelayAlarm.Click
        Dim nextAlarmDate As Date
        Dim trigger As Quartz.Impl.Triggers.CronTriggerImpl

        HideAlarmNotification()

        ' Delay alarm play for another 5 minutes
        nextAlarmDate = Now.AddMinutes(5)

        ClearAlarmTriggers()

        trigger = New Quartz.Impl.Triggers.CronTriggerImpl
        trigger.CronExpression = New Quartz.CronExpression(GetChronDate(nextAlarmDate))
        trigger.Name = "clockTrigger"

        sched.ScheduleJob(job, trigger)

    End Sub

    Private Sub ShowScreen()
        Me.Visibility = Windows.Visibility.Visible
        Me.Activate()

        ' Load background sound
        If mpBackgroundSound.Source Is Nothing Then LoadBackgroundSound()
        preventSleepingTimer.Change(0, MINUTES * 5)

    End Sub

    Private Sub HideScreen()
        Me.Visibility = Windows.Visibility.Collapsed

        ' Turn off background sound
        mpBackgroundSound.Source = Nothing

        ' Turn off alarm if playing
        mpAlarm.Source = Nothing
        RestoreSleeping()

    End Sub

    Private Sub HideAlarmNotification()
        mpAlarm.Source = Nothing
        pnlAlarmNotification.Visibility = Windows.Visibility.Collapsed

    End Sub

    Private Sub UpdateTime(state As Object)
        Dim disp As System.Windows.Threading.Dispatcher

        disp = DirectCast(state, System.Windows.Threading.Dispatcher)

        disp.BeginInvoke(Sub() txtTime.Text = Now.ToString("h:mm tt"))
        disp.BeginInvoke(Sub() txtDate.Text = Now.ToLongDateString)

    End Sub

    Private Sub PreventSleeping(state As Object)
        Try
            SetThreadExecutionState(ES_CONTINUOUS Or ES_AWAYMODE_REQUIRED)

        Catch ex As Exception

        End Try
    End Sub

    Private Sub RestoreSleeping()
        Try
            SetThreadExecutionState(ES_CONTINUOUS)

        Catch ex As Exception

        End Try
    End Sub

    Private Sub PowerOffMonitor()
        Dim hlp As System.Windows.Interop.WindowInteropHelper
        Dim h As IntPtr

        Try
            hlp = New System.Windows.Interop.WindowInteropHelper(Me)
            h = hlp.Handle

            SendMessage(h, WM_SYSCOMMAND, CType(SC_MONITORPOWER, IntPtr), CType(MONITOR_OFF, IntPtr))

        Catch ex As Exception

        End Try

    End Sub

    Private Sub DockChanged(s As Object, e As NICPoller.NICPollEventArgs)
        If e.IsConnected Then
            Me.Dispatcher.BeginInvoke(Sub() ShowScreen())
        Else
            Me.Dispatcher.BeginInvoke(Sub() HideScreen())
        End If

    End Sub

    Private Sub BatteryChanged(s As Object, e As NICPoller.BatteryPollEventArgs)
        txtBattery.Text = String.Format("{0}%", e.EstimatedChargeRemaining)
    End Sub

    Private Sub SettingsChanged(s As Object, e As ucSettings.SettingsChangedEventArgs)
        Select Case e.ChangedSetting
            Case ucSettings.SettingsChangedEventArgs.ChangedSettingEnum.AlarmEnabled
                SetAlarm()
            Case ucSettings.SettingsChangedEventArgs.ChangedSettingEnum.AlarmTime
                SetAlarm()
            Case ucSettings.SettingsChangedEventArgs.ChangedSettingEnum.AlarmSound
                SetAlarm()
            Case ucSettings.SettingsChangedEventArgs.ChangedSettingEnum.BackgroundImage
                LoadBackground()
            Case ucSettings.SettingsChangedEventArgs.ChangedSettingEnum.BackgroundPlayMinutes
                SetBackgroundSoundTimeout()
            Case ucSettings.SettingsChangedEventArgs.ChangedSettingEnum.BackgroundSound
                LoadBackgroundSound()
            Case ucSettings.SettingsChangedEventArgs.ChangedSettingEnum.BackgroundSoundEnabled
                LoadBackgroundSound()
        End Select

        My.Settings.Save()

    End Sub

    Private Sub LoadBackground()
        Try
            If IO.File.Exists(My.Settings.BackgroundImage) Then
                mpBackground.Source = New Uri(My.Settings.BackgroundImage, UriKind.Absolute)
                mpBackground.Visibility = Windows.Visibility.Visible
            Else
                mpBackground.Visibility = Windows.Visibility.Collapsed
            End If

        Catch ex As Exception
            MsgBox("Could not set background", MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly)
        End Try

    End Sub

    Private Sub mpBackground_MediaEnded(sender As Object, e As RoutedEventArgs) Handles mpBackground.MediaEnded
        mpBackground.Position = New TimeSpan(0)
    End Sub

    Private Sub LoadBackgroundSound()
        If My.Settings.BackgroundSoundEnabled AndAlso IO.File.Exists(My.Settings.BackgroundSound) Then
            mpBackgroundSound.Source = New Uri(My.Settings.BackgroundSound, UriKind.Absolute)

            SetBackgroundSoundTimeout()
        Else
            mpBackgroundSound.Source = Nothing
        End If
    End Sub

    Private Sub StopBackgroundSound(state As Object)
        DirectCast(state, Windows.Threading.Dispatcher).BeginInvoke(Sub() mpBackgroundSound.Source = Nothing)

    End Sub

    Private Sub SetBackgroundSoundTimeout()
        If backgroundSoundTimer Is Nothing Then
            backgroundSoundTimer = New Timer(AddressOf StopBackgroundSound, Me.Dispatcher, -1, 0)
        End If

        If My.Settings.BackgroundPlayMinutes > 0 Then backgroundSoundTimer.Change(MINUTES * My.Settings.BackgroundPlayMinutes, 0)

    End Sub

    Private Sub mpBackgroundSound_MediaEnded(sender As Object, e As RoutedEventArgs) Handles mpBackgroundSound.MediaEnded
        mpBackgroundSound.Position = New TimeSpan(0)

    End Sub

    Private Sub SetAlarm()
        Dim nextAlarmDate As Date
        Dim trigger As Quartz.Impl.Triggers.CronTriggerImpl

        Try
            ' Turn off any alarm already playing
            mpAlarm.Source = Nothing

            If My.Settings.AlarmEnabled AndAlso Not String.IsNullOrEmpty(My.Settings.AlarmDayMask) Then
                nextAlarmDate = Now.Date.Add(My.Settings.AlarmTime.TimeOfDay)

                ' If the time has passed, move to next day
                If nextAlarmDate < Now Then nextAlarmDate = nextAlarmDate.AddDays(1)

                ' Increment day until next alarm day
                Do While Not IsAlarmDate(nextAlarmDate)
                    nextAlarmDate = nextAlarmDate.AddDays(1)
                Loop

                ' Clear previous schedule triggers
                ClearAlarmTriggers()

                trigger = New Quartz.Impl.Triggers.CronTriggerImpl
                With trigger
                    .Name = "clockTrigger"
                    .CronExpression = New Quartz.CronExpression(GetChronDate(nextAlarmDate))
                End With

                sched.ScheduleJob(job, trigger)

                pnlAlarm.Visibility = Windows.Visibility.Visible
                txtAlarmTime.Text = nextAlarmDate.ToShortTimeString
                If nextAlarmDate.Day <> Now.Day Then txtAlarmTime.Text &= " (" & nextAlarmDate.DayOfWeek.ToString.Substring(0, 3) & ")"

            Else
                ClearAlarmTriggers()

                pnlAlarm.Visibility = Windows.Visibility.Collapsed
            End If

        Catch ex As Exception
            ClearAlarmTriggers()

            pnlAlarm.Visibility = Windows.Visibility.Collapsed
            MsgBox("Could not set alarm time.", MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly)

        End Try

    End Sub

    Public Sub PlayAlarm()
        Try
            If IO.File.Exists(My.Settings.AlarmSound) Then
                ShowScreen()
                mpBackgroundSound.Source = Nothing
                mpAlarm.Source = New Uri(My.Settings.AlarmSound, UriKind.Absolute)
                pnlAlarmNotification.Visibility = Windows.Visibility.Visible

            Else
                mpAlarm.Source = Nothing

            End If

        Catch ex As Exception
            MsgBox("Could not play alarm file.", MsgBoxStyle.Exclamation Or MsgBoxStyle.OkOnly)

        End Try

    End Sub

    Private Function IsAlarmDate(alarmDate As Date) As Boolean
        Dim mask As String = My.Settings.AlarmDayMask

        If alarmDate.DayOfWeek = DayOfWeek.Monday AndAlso mask.IndexOf("Mon") >= 0 Then Return True
        If alarmDate.DayOfWeek = DayOfWeek.Tuesday AndAlso mask.IndexOf("Tue") >= 0 Then Return True
        If alarmDate.DayOfWeek = DayOfWeek.Wednesday AndAlso mask.IndexOf("Wed") >= 0 Then Return True
        If alarmDate.DayOfWeek = DayOfWeek.Thursday AndAlso mask.IndexOf("Thu") >= 0 Then Return True
        If alarmDate.DayOfWeek = DayOfWeek.Friday AndAlso mask.IndexOf("Fri") >= 0 Then Return True
        If alarmDate.DayOfWeek = DayOfWeek.Saturday AndAlso mask.IndexOf("Sat") >= 0 Then Return True
        If alarmDate.DayOfWeek = DayOfWeek.Saturday AndAlso mask.IndexOf("Sun") >= 0 Then Return True

        Return False

    End Function

    Private Function GetChronDate(alarmDate As Date) As String
        Return String.Format("{0} {1} {2} {3} {4} {5}", alarmDate.Second, alarmDate.Minute, alarmDate.Hour, alarmDate.Day, alarmDate.Month, "?")
    End Function

    Private Sub ClearAlarmTriggers()
        For Each t As Quartz.ITrigger In sched.GetTriggersOfJob(job.Key)
            sched.UnscheduleJob(t.Key)
        Next
    End Sub

#Region "Systray Icon and Menu"

    Private Sub SetupDockIcon()
        Dim menu As Forms.ContextMenuStrip
        Dim item As Forms.ToolStripMenuItem

        menu = New Forms.ContextMenuStrip

        item = New Forms.ToolStripMenuItem
        With item
            .Name = "mnuShowForm"
            .Text = "Show"
        End With
        AddHandler item.Click, AddressOf mnuShowForm_Click
        menu.Items.Add(item)

        item = New Forms.ToolStripMenuItem
        With item
            .Name = "mnuExit"
            .Text = "Exit"
        End With
        AddHandler item.Click, AddressOf mnuExit_Click
        menu.Items.Add(item)

        trayIcon = New Forms.NotifyIcon
        With trayIcon
            .Text = "DuoDock"
            .Icon = My.Resources.clock
            .ContextMenuStrip = menu
        End With
        AddHandler trayIcon.DoubleClick, AddressOf mnuShowForm_Click

    End Sub

    Private Sub mnuExit_Click(sender As Object, e As EventArgs)
        Me.Close()
    End Sub

    Private Sub mnuShowForm_Click(s As Object, e As EventArgs)
        ShowScreen()

    End Sub

#End Region

End Class
